package com.nipun.neural.utils.io;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.xml.serialize.DOMSerializer;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.nipun.graph.utils.XMLUtils;
import com.nipun.neural.objects.Network;
import com.nipun.neural.objects.Neuron;
import com.nipun.neural.objects.Synapse;
import com.nipun.neural.objects.networks.NetworkImpl;
import com.nipun.neural.objects.synapses.SynapseImpl;
import com.nipun.neural.utils.PropertiesManager;

public class DiskIOUtils {

	public Network ConstructNetwotkFromXMLDocument(String filePath){
		Network net = new NetworkImpl();
		Document graphDocument = XMLUtils.getDOM(new File(filePath));
		Element docElement = graphDocument.getDocumentElement();
		Object [] vertices = XMLUtils.evalXPath(docElement, "//Network/Neuron", null);
		
		for(int i=0; i<vertices.length;i++){
			Node temp = (Node) vertices[i];
			Node labelNode = temp.getFirstChild().getNextSibling();
			String labelStr = labelNode.getFirstChild().getNodeValue();
			Node typeNode = labelNode.getNextSibling().getNextSibling();
			String typeStr = typeNode.getFirstChild().getNodeValue();
			net.addNode(getNeuronImpl(labelStr, typeStr));
		}
		
		Object[] edges = XMLUtils.evalXPath(docElement, "//Network/Synapse", null);
		for(int i=0; i<edges.length; i++){
			Node temp = (Node)edges[i];
			Element el = (Element)temp;
			Neuron n1 = null;
			Neuron n2 = null;
			Node firstNode = temp.getFirstChild().getNextSibling();
			if(firstNode != null){
				String name1 = firstNode.getFirstChild().getNodeValue();
				if(net.lookUpNode(name1) != null){
					n1 = (Neuron)net.lookUpNode(name1);
				}else{
					System.out.println("edge refers to non-existant node");
					return null;
				}
			}
			Node secondNode = firstNode.getNextSibling().getNextSibling();
			if(secondNode != null){
				String name2 = secondNode.getFirstChild().getNodeValue();
				if(net.lookUpNode(name2) != null){
					n2 = (Neuron)net.lookUpNode(name2);
				}else{
					System.out.println("edge refers to non-existant node");
					return null;
				}
			}
			double weight = 0;
			Node thirdNode = secondNode.getNextSibling().getNextSibling();
			if(thirdNode!=null){
				String w = thirdNode.getFirstChild().getNodeValue();
				Double weightStr = new Double(w);
				weight = weightStr.doubleValue();
			}
			
			net.addEdge(new SynapseImpl(n1,n2,weight));
			
		}
		Object[] fireOrderObj = XMLUtils.evalXPath(docElement, "//Network/FiringOrder", null);
		Node fireOrder = (Node)fireOrderObj[0];
		String fo = fireOrder.getFirstChild().getNodeValue();
		net.setFiringOrder(neuronTokenizer(fo, "-", net));
		
		Object[] inputNodesObj = XMLUtils.evalXPath(docElement, "//Network/InputNodes", null);
		Node inputNodes = (Node)inputNodesObj[0];
		String in = inputNodes.getFirstChild().getNodeValue();
		net.setInputNodes(neuronTokenizer(in, "-", net));
		
		Object[] outputNodesObj = XMLUtils.evalXPath(docElement, "//Network/OutputNodes", null);
		Node outputNodes = (Node)outputNodesObj[0];
		String on = outputNodes.getFirstChild().getNodeValue();
		net.setOutputNodes(neuronTokenizer(on, "-", net));
		
		Object[] memNodesObj = XMLUtils.evalXPath(docElement, "//Network/MemoryNodes", null);
		Node memoryNodes = (Node)memNodesObj[0];
		if(memoryNodes!=null && memoryNodes.getFirstChild()!=null){
			String mn = memoryNodes.getFirstChild().getNodeValue();
			net.setMemoryNodes(neuronTokenizer(mn, "-", net));
		}
		
		return net;
	}
	
	private List neuronTokenizer(String fo, String token, Network net){
		List order = new ArrayList();
		StringTokenizer tokenizer = new StringTokenizer(fo,token);
		while(tokenizer.hasMoreTokens()){
			String curr = tokenizer.nextToken();
			if(net.lookUpNode(curr)==null){
				System.out.println("tokenizer has a neuron that doesnt exist...exiting");
				return null;
			}
			order.add(net.lookUpNode(curr));
		}
		return order;
	}
	
	private Neuron getNeuronImpl(String label, String type){
		if(type == null || "".equals(type)){
			return null;
		}else {
			Neuron neuronObj = null;
			Class neuronClass;
			Constructor constructor;
			try {
				neuronClass = Class.forName(getClassName(type));
				constructor = neuronClass.getConstructor(Class.forName("java.lang.String"));
				neuronObj = (Neuron) constructor.newInstance(label);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			if(neuronObj == null){
				System.out.println("could not find a neuron of type :: " + type);
			}
			return neuronObj;
		}
	}
	
	private String getClassName(String type){
		PropertiesManager propManager = PropertiesManager.getInstance();
		Properties props = propManager.getProperties();
		return props.getProperty(type);
	}
	
	public void writeNetworkToDisk(Network net, String filePath){
		Document doc = createDocumentFromNetwork(net);
		StringWriter sw = new StringWriter();
		DOMSerializer ser = new XMLSerializer(sw, new OutputFormat());
		try {
			ser.serialize(doc);
			String str = sw.toString();
			FileWriter fw = new FileWriter(new File(filePath));
			fw.write(str);
			fw.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Document createDocumentFromNetwork(Network net){
		Document graphDocument = XMLUtils.getDOM(new File("EmptyNetworkDocument.xml"));
		Element docElement = graphDocument.getDocumentElement();
		//first we get all the nodes we need
		Node neuronNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/Neuron", null)[0]);
		Node synapseNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/Synapse", null)[0]);
		//done...other nodes we only need once
		
		Object[] neurons = net.getVertices();
		for(int i=0; i<neurons.length; i++){
			Neuron n = (Neuron)neurons[i];
			docElement.appendChild(getNewNeuronNode(neuronNode, graphDocument, n.getLabel(), getNeuronType(n)));
		}
		
		Object[] synapses = net.getEdges();
		for(int i=0; i<synapses.length; i++){
			Synapse s = (Synapse)synapses[i];
			docElement.appendChild(getNewSynapseNode(synapseNode, graphDocument, s.getNode1().getLabel(), s.getNode2().getLabel(), ""+s.getWeight()));
		}
		
		//make input nodes string
		Object[] in = net.getInputNodes();
		String ins = "";
		if(in.length==1){
			ins = ((Neuron)in[0]).getLabel();
		}else{
			ins = ((Neuron)in[0]).getLabel();
			for(int i=1; i<in.length; i++){
				ins = ins + "-" + ((Neuron)in[i]).getLabel();
			}
		}
		
		Object[] on = net.getOutputNodes();
		String ons = "";
		if(on.length==1){
			ons = ((Neuron)on[0]).getLabel();
		}else{
			ons = ((Neuron)on[0]).getLabel();
			for(int i=1; i<on.length; i++){
				ons = ons + "-" + ((Neuron)on[i]).getLabel();
			}
		}
		
		Object[] mn = net.getMemoryNodes();
		String mns = "";
		if(mn.length==1){
			mns = ((Neuron)mn[0]).getLabel();
		}else{
			mns = ((Neuron)mn[0]).getLabel();
			for(int i=1; i<mn.length; i++){
				mns = mns + "-" + ((Neuron)mn[i]).getLabel();
			}
		}
		
		Object[] fo = net.getFiringOrder();
		String fos = "";
		if(fo.length==1){
			fos = ((Neuron)fo[0]).getLabel();
		}else{
			fos = ((Neuron)fo[0]).getLabel();
			for(int i=1; i<fo.length; i++){
				fos = fos + "-" + ((Neuron)fo[i]).getLabel();
			}
		}
		
		Node inNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/InputNodes", null)[0]);
		Node onNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/OutputNodes", null)[0]);
		Node mnNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/MemoryNodes", null)[0]);
		Node foNode = (Node)(XMLUtils.evalXPath(docElement, "//Network/FiringOrder", null)[0]);
		
		inNode.appendChild(graphDocument.createTextNode(ins));
		onNode.appendChild(graphDocument.createTextNode(ons));
		mnNode.appendChild(graphDocument.createTextNode(mns));
		foNode.appendChild(graphDocument.createTextNode(fos));
		//add all new nodes before this....
		//removing original nodes
		docElement.removeChild(neuronNode);
		docElement.removeChild(synapseNode);

		graphDocument.normalize();
		return graphDocument;
	}
	
	private Node getNewNeuronNode(Node origNode, Document doc, String label, String type){
		Node neuronNode = origNode.cloneNode(true);
		Node labelNode = neuronNode.getFirstChild().getNextSibling();
		Node textNode = doc.createTextNode(label);
		labelNode.appendChild(textNode);
		Node typeNode = labelNode.getNextSibling().getNextSibling();
		Node textNode1 = doc.createTextNode(type);
		typeNode.appendChild(textNode1);
		return neuronNode;
	}
	
	private Node getNewSynapseNode(Node origNode, Document doc, String n1, String n2, String w){
		Node synapseNode = origNode.cloneNode(true);
		Node n1TextNode = doc.createTextNode(n1);
		Node n2TextNode = doc.createTextNode(n2);
		Node wTextNode = doc.createTextNode(w);
		Node neuron1 = synapseNode.getFirstChild().getNextSibling();
		neuron1.appendChild(n1TextNode);
		Node neuron2 = neuron1.getNextSibling().getNextSibling();
		neuron2.appendChild(n2TextNode);
		Node weight = neuron2.getNextSibling().getNextSibling();
		weight.appendChild(wTextNode);
		return synapseNode;
	}
	
	private String getNeuronType(Neuron n){
		PropertiesManager propManager = PropertiesManager.getInstance();
		Properties props = propManager.getProperties();
		String className = n.getClass().getName();
		return props.getProperty(className);
	}
}
